import { Fragment, useCallback, useContext, useMemo, useState } from "react";
import { Group, Label, Path, Rect, Tag, Text } from "react-konva";
import { observer } from "mobx-react";
import { getRoot } from "mobx-state-tree";

import Utils from "../../utils";
import Constants from "../../core/Constants";
import { ImageViewContext } from "./ImageViewContext";

const NON_ADJACENT_CORNER_RADIUS = 4;
const ADJACENT_CORNER_RADIUS = [4, 4, 0, 0];
const TAG_PATH =
  "M13.47,2.52c-0.27-0.27-0.71-0.27-1.59-0.27h-0.64c-1.51,0-2.26,0-2.95,0.29C7.61,2.82,7.07,3.35,6,4.43L3.65,6.78c-0.93,0.93-1.4,1.4-1.4,1.97c0,0.58,0.46,1.04,1.39,1.97l1.63,1.63c0.93,0.93,1.39,1.39,1.97,1.39s1.04-0.46,1.97-1.39L11.57,10c1.07-1.07,1.61-1.61,1.89-2.29c0.28-0.68,0.28-1.44,0.28-2.96V4.11C13.74,3.23,13.74,2.8,13.47,2.52z M10.5,6.9c-0.77,0-1.4-0.63-1.4-1.4s0.63-1.39,1.4-1.39s1.39,0.63,1.39,1.4S11.27,6.9,10.5,6.9z";
const OCR_PATH =
  "M13,1v2H6C4.11,3,3.17,3,2.59,3.59C2,4.17,2,5.11,2,7v2c0,1.89,0,2.83,0.59,3.41C3.17,13,4.11,13,6,13h7v2h1V1H13z M6,9.5C5.17,9.5,4.5,8.83,4.5,8S5.17,6.5,6,6.5S7.5,7.17,7.5,8S6.83,9.5,6,9.5z M11,9.5c-0.83,0-1.5-0.67-1.5-1.5s0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5S11.83,9.5,11,9.5z";

const LabelOnBbox = ({
  x,
  y,
  text,
  score,
  showLabels,
  rotation = 0,
  zoomScale = 1,
  color,
  maxWidth,
  onClickLabel,
  onMouseEnterLabel,
  onMouseLeaveLabel,
  adjacent = false,
  isTexting = false,
}) => {
  const fontSize = 13;
  const height = 20;
  const scale = 1 / zoomScale;
  const [textEl, setTextEl] = useState();
  const paddingLeft = 20;
  const paddingRight = 5;
  const scoreSpace = score ? 34 : 0;
  const horizontalPaddings = paddingLeft + paddingRight;
  const textMaxWidth = Math.max(0, maxWidth * zoomScale - horizontalPaddings - scoreSpace);
  const isSticking = !!textMaxWidth;
  const { suggestion } = useContext(ImageViewContext) ?? {};

  const width = useMemo(() => {
    if (!showLabels || !textEl || !maxWidth) return null;
    const currentTextWidth = text ? textEl.measureSize(text).width : 0;

    if (currentTextWidth > textMaxWidth) {
      return textMaxWidth;
    }
    return null;
  }, [textEl, text, maxWidth, scale]);

  const tagSceneFunc = useCallback(
    (context, shape) => {
      const cornerRadius = adjacent && isSticking ? ADJACENT_CORNER_RADIUS : NON_ADJACENT_CORNER_RADIUS;
      const width = maxWidth
        ? Math.min(shape.width() + horizontalPaddings, isSticking ? maxWidth * zoomScale : paddingLeft)
        : shape.width() + horizontalPaddings;
      const height = shape.height();

      context.beginPath();
      if (!cornerRadius) {
        context.rect(0, 0, width, height);
      } else {
        let topLeft = 0;
        let topRight = 0;
        let bottomLeft = 0;
        let bottomRight = 0;

        if (typeof cornerRadius === "number") {
          topLeft = topRight = bottomLeft = bottomRight = Math.min(cornerRadius, width / 2, height / 2);
        } else {
          topLeft = Math.min(cornerRadius[0], width / 2, height / 2);
          topRight = Math.min(cornerRadius[1], width / 2, height / 2);
          bottomRight = Math.min(cornerRadius[2], width / 2, height / 2);
          bottomLeft = Math.min(cornerRadius[3], width / 2, height / 2);
        }
        context.moveTo(topLeft, 0);
        context.lineTo(width - topRight, 0);
        context.arc(width - topRight, topRight, topRight, (Math.PI * 3) / 2, 0, false);
        context.lineTo(width, height - bottomRight);
        context.arc(width - bottomRight, height - bottomRight, bottomRight, 0, Math.PI / 2, false);
        context.lineTo(bottomLeft, height);
        context.arc(bottomLeft, height - bottomLeft, bottomLeft, Math.PI / 2, Math.PI, false);
        context.lineTo(0, topLeft);
        context.arc(topLeft, topLeft, topLeft, Math.PI, (Math.PI * 3) / 2, false);
      }
      context.closePath();
      context.fillStrokeShape(shape);
    },
    [adjacent, isSticking, maxWidth],
  );

  if (!showLabels) return null;

  return (
    <Group strokeScaleEnabled={false} x={x} y={y} rotation={rotation}>
      {!!score && (
        <Label
          y={-height * scale}
          scaleX={scale}
          scaleY={scale}
          onClick={() => {
            return false;
          }}
        >
          <Tag fill={Utils.Colors.getScaleGradient(score)} cornerRadius={2} />
          <Text
            text={score.toFixed(2)}
            fontFamily="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif"
            fontSize={fontSize}
            fill="white"
            padding={0}
            lineHeight={(1 / fontSize) * height}
          />
        </Label>
      )}
      <Label
        x={paddingLeft * scale + scoreSpace * scale}
        y={-height * scale}
        scaleX={scale}
        scaleY={scale}
        onClick={onClickLabel}
        onMouseEnter={onClickLabel ? onMouseEnterLabel : null}
        onMouseLeave={onClickLabel ? onMouseLeaveLabel : null}
        listening={!suggestion}
      >
        <Tag fill={color} cornerRadius={4} sceneFunc={tagSceneFunc} offsetX={paddingLeft} />
        <Text
          ref={setTextEl}
          text={text}
          fontFamily="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif"
          fontSize={fontSize}
          lineHeight={(1 / fontSize) * height}
          height={height}
          width={width}
          wrap="none"
          ellipsis="true"
          fill={Constants.SHOW_LABEL_FILL}
          padding={0}
        />
      </Label>
      <Path
        x={2 * scale + scoreSpace * scale}
        y={2 * scale - height * scale}
        scaleX={scale}
        scaleY={scale}
        fill={Constants.SHOW_LABEL_FILL}
        data={isTexting ? OCR_PATH : TAG_PATH}
      />
    </Group>
  );
};

const LabelOnEllipse = observer(({ item, color, strokewidth }) => {
  if (!item.parent) return null;
  const isTexting = !!item.texting;
  const labelText = item.getLabelText(",");
  const obj = item.parent;
  const zoomScale = item.parent.zoomScale || 1;

  return (
    <LabelOnBbox
      x={obj.internalToCanvasX(item.x - item.radiusX) - strokewidth / 2 / zoomScale}
      y={obj.internalToCanvasY(item.y - item.radiusY) - strokewidth / 2 / zoomScale}
      isTexting={isTexting}
      text={labelText}
      score={item.score}
      showLabels={getRoot(item).settings.showLabels}
      zoomScale={item.parent.zoomScale}
      color={color}
      onClickLabel={item.onClickLabel}
    />
  );
});

const LabelOnRect = observer(({ item, color, strokewidth }) => {
  if (!item.parent) return null;
  const isTexting = !!item.texting;
  const labelText = item.getLabelText(",");
  const obj = item.parent;
  const zoomScale = item.parent.zoomScale || 1;

  return (
    <LabelOnBbox
      x={obj.internalToCanvasX(item.x) - strokewidth / 2 / zoomScale}
      y={obj.internalToCanvasY(item.y) - strokewidth / 2 / zoomScale}
      isTexting={isTexting}
      text={labelText}
      score={item.score}
      showLabels={getRoot(item).settings.showLabels}
      zoomScale={item.parent.zoomScale}
      rotation={item.rotation}
      color={color}
      maxWidth={obj.internalToCanvasX(item.width) + strokewidth}
      adjacent
      onClickLabel={item.onClickLabel}
    />
  );
});

const LabelOnPolygon = observer(({ item, color }) => {
  if (!item.parent) return null;
  const isTexting = !!item.texting;
  const labelText = item.getLabelText(",");
  const bbox = item.bboxCoordsCanvas;

  if (!bbox) return null;

  const settings = getRoot(item).settings;

  return (
    <Fragment>
      {settings.showLabels && (
        <Rect
          x={bbox.left}
          y={bbox.top}
          fillEnabled={false}
          width={bbox.right - bbox.left}
          height={bbox.bottom - bbox.top}
          stroke={item.style?.strokecolor}
          strokeWidth={1}
          strokeScaleEnabled={false}
          shadowBlur={0}
        />
      )}
      <LabelOnBbox
        x={bbox.left}
        y={bbox.top + 2 / item.parent.zoomScale}
        isTexting={isTexting}
        text={labelText}
        score={item.score}
        showLabels={settings.showLabels}
        zoomScale={item.parent.zoomScale}
        color={color}
        onClickLabel={item.onClickLabel}
      />
    </Fragment>
  );
});

const LabelOnMask = observer(({ item, color }) => {
  if (!item.parent) return null;
  const settings = getRoot(item).settings;

  if (!settings.showLabels) return null;

  const isTexting = !!item.texting;
  const labelText = item.getLabelText(",");
  const bbox = item.bboxCoordsCanvas;

  if (!bbox) return null;

  return (
    <Group name="region-label">
      <Rect
        x={bbox.left}
        y={bbox.top}
        fillEnabled={false}
        width={bbox.right - bbox.left}
        height={bbox.bottom - bbox.top}
        stroke={item.style?.strokecolor}
        strokeWidth={1}
        strokeScaleEnabled={false}
        shadowBlur={0}
      />
      <LabelOnBbox
        x={bbox.left}
        y={bbox.top + 2 / item.parent.zoomScale}
        isTexting={isTexting}
        text={labelText}
        score={item.score}
        showLabels={settings.showLabels}
        zoomScale={item.parent.zoomScale}
        color={color}
        onClickLabel={item.onClickLabel}
      />
    </Group>
  );
});

const LabelOnKP = observer(({ item, color }) => {
  if (!item.parent) return null;
  const isTexting = !!item.texting;
  const labelText = item.getLabelText(",");

  return (
    <LabelOnBbox
      // keypoints' width scaled back to stay always small, so scale it here too
      x={item.canvasX + (item.canvasWidth + 2) / item.parent.zoomScale}
      y={item.canvasY + (item.canvasWidth + 2) / item.parent.zoomScale}
      isTexting={isTexting}
      text={labelText}
      score={item.score}
      showLabels={getRoot(item).settings.showLabels}
      zoomScale={item.parent.zoomScale}
      color={color}
      onClickLabel={item.onClickLabel}
    />
  );
});

const LabelOnVideoBbox = observer(({ reg, box, color, scale, strokeWidth, adjacent = false }) => {
  const isTexting = !!reg.texting;
  const labelText = reg.getLabelText(",");

  return (
    <LabelOnBbox
      x={box.x}
      y={box.y}
      rotation={box.rotation}
      isTexting={isTexting}
      text={labelText}
      score={reg.score}
      showLabels={reg.store.settings.showLabels}
      zoomScale={scale}
      color={color}
      maxWidth={box.width + strokeWidth}
      adjacent={adjacent}
      onClickLabel={reg.onClickRegion}
    />
  );
});

const LabelOnOcrBox = observer(({ region, color, strokeWidth = 1, viewRect, pageRotationDeg = 0, zoomScale = 1 }) => {
  if (!region?.store) return null;

  const labelText = region.getLabelText(",") || region?.ocrtext || region?.noLabelView || "";
  const showLabels = region.store.settings?.showLabels;
  const isTexting = Boolean(region.texting || region.ocrtext);
  const rotation = ((((region.rotation ?? 0) + (pageRotationDeg ?? 0)) % 360) + 360) % 360;
  const safeStrokeWidth = typeof strokeWidth === "number" ? strokeWidth : 0;
  const rect = viewRect ?? { x: 0, y: 0, width: 0, height: 0 };
  const width = Math.max(1, rect.width);

  return (
    <LabelOnBbox
      x={rect.x - safeStrokeWidth / 2 / zoomScale}
      y={rect.y - safeStrokeWidth / 2 / zoomScale}
      isTexting={isTexting}
      text={labelText}
      score={region.score}
      showLabels={showLabels}
      zoomScale={zoomScale}
      rotation={rotation}
      color={color}
      maxWidth={width + safeStrokeWidth}
      adjacent
      onClickLabel={region.onClickRegion}
    />
  );
});

export {
  LabelOnBbox,
  LabelOnPolygon,
  LabelOnRect,
  LabelOnEllipse,
  LabelOnKP,
  LabelOnMask,
  LabelOnVideoBbox,
  LabelOnOcrBox,
};
